package model;

import operand.MyError;
import operand.MyStop;
import operand.MyValue;
import operand.Operand;
import operand.OperandVisitor;
import states.FirstOperandAvailable;
import states.NoOperandsAvailable;
import states.OperationState;
import states.OperationStateVisitor;
import states.OperationStopped;
import states.SecondOperandAvailable;
import buffer.Source;

public abstract class Operation<T> implements HasSource<Operand<T>>{
	
	private final Source<Operand<T>> firstInputSource;
	private final Source<Operand<T>> secondInputSource;
	private final HasSource<Operand<T>> output;
	private boolean threadRunning;
	private OperationState<T> state;
	private final lock.Lock stateCriticalSection = new lock.ConcreteLock(false);
	private final lock.Lock firstInputLock = new lock.ConcreteLock(true);
	private final lock.Lock secondInputLock = new lock.ConcreteLock(true);

	public lock.Lock getStateCriticalSection() {
		return stateCriticalSection;
	}
	public lock.Lock getFirstInputLock(){
		return this.firstInputLock;
	}
	public lock.Lock getSecondInputLock(){
		return this.secondInputLock;
	}
	public boolean isThreadRunning() {
		return threadRunning;
	}

	public void setThreadRunning(final boolean threadRunning) {
		this.threadRunning = threadRunning;
	}

	private HasSource<Operand<T>> getOutput() {
		return output;
	}
	
	protected Source<Operand<T>> getFirstInputSource(){
		return this.firstInputSource;
	}
	
	protected Source<Operand<T>> getSecondInputSource(){
		return this.secondInputSource;
	}

	public OperationState<T> getState() {
		return this.state;
	}
	
	public void setState(final OperationState<T> state){
		state.accept(new OperationStateVisitor<T>() {

			@Override
			public void visit(final NoOperandsAvailable<T> noOperandsAvailable) {
				this.unlockInputLocks();
			}

			@Override
			public void visit(final FirstOperandAvailable<T> firstOperandAvailable) {
				
			}

			@Override
			public void visit(final SecondOperandAvailable<T> secondOperandAvailable) {
				
			}

			@Override
			public void visit(final OperationStopped<T> operationStopped) {
				this.unlockInputLocks();
			}

			private void unlockInputLocks(){
				Operation.this.getFirstInputLock().unlock();
				Operation.this.getSecondInputLock().unlock();				
				
			}

		});

		this.state = state;
	}
	
	/**
	 * Constructor of {@link Operation}.
	 * @param first is the {@link Source} of the first operand.
	 * @param second is the {@link Source} of the second operand.
	 */
	public Operation(final HasSource<Operand<T>> first,
					 final HasSource<Operand<T>> second){
		this.firstInputSource = first.getSource();
		this.secondInputSource = second.getSource();
		this.state = new NoOperandsAvailable<T>();
		if(this.getFirstInputSource().isConstant() &&
		   this.getSecondInputSource().isConstant()){
			this.output = new Constant<Operand<T>>(
					this.calculate(this.getFirstInputSource().get(),
								   this.getSecondInputSource().get()));
		} else{
			final Variable<Operand<T>> output = new Variable<Operand<T>>();
			this.output = output;
			this.setThreadRunning(true);			
			new Thread(new FirstInputRunnable<T>(this, output),this.toString() + " first input thread.").start();
			new Thread(new SecondInputRunnable<T>(this, output),this.toString() + " second input thread.").start();
		}
	}
	
	protected Operand<T> calculate(final Operand<T> first,
			final Operand<T> second) {
		return first.accept(new OperandVisitor<T>() {

			@Override
			public Operand<T> visit(final MyValue<T> value) {
				return second.accept(new OperandVisitor<T>() {

					@Override
					public Operand<T> visit(final MyValue<T> secondValue) {
						return Operation.this.calculateTransitive(value, secondValue);
					}

					@Override
					public MyError<T> visit(final MyError<T> error) {
						return error;
					}

					@Override
					public MyStop<T> visit(final MyStop<T> stop) {
						Operation.this.setThreadRunning(false);
						return stop;
					}
				});
			}

			@Override
			public MyError<T> visit(final MyError<T> error) {
				return error;
			}

			@Override
			public MyStop<T> visit(final MyStop<T> stop) {
				Operation.this.setThreadRunning(false);
				return stop;
			}
		});
	}
	protected abstract Operand<T> calculateTransitive(MyValue<T> firstValue, MyValue<T> secondValue);
	
	@Override
	public Source<Operand<T>> getSource() {
		return this.getOutput().getSource();
	}

	@Override
	public void accept(final HasSourceVisitor<Operand<T>> visitor) {
		visitor.visit(this);
	}
}
